package cn.yuxirey.verifyedittext;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.IntRange;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.text.InputType;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.PopupWindow;

/**
 * 验证码输入框（暂时支持数字类型）
 */
public class VerifyEditText extends View {
    private final static int DEFAULT_LINE_HEIGHT = 1;
    private final static int DEFAULT_LINE_MARGIN_TOP = 16;
    private final static int DEFAULT_INACTIVE_COLOR = Color.parseColor("#999999");
    private final static int DEFAULT_ACTIVE_COLOR = Color.parseColor("#333333");

    @ColorInt
    private int mTextInactiveColor;
    @ColorInt
    private int mTextActiveColor;
    private int mTextSize;
    @IntRange(from = 4, to = 6)
    private int mMaxLength;
    private Paint mPaint;
    private Rect mTextBound = new Rect();
    private StringBuilder mCodeStr = new StringBuilder("");
    @Nullable
    private OnCompleteListener mCompleteListener;
    @Nullable
    private OnKeyEventListener onKeyEventListener;
    @ColorInt
    private int mLineInactiveColor;
    @ColorInt
    private int mLineActiveColor;
    private int mTextStyle;
    private int mLineMarginTop;
    private int mLIneHeight;
    private int mLineWidth;
    private boolean mOnlyShownLine = false;

    private PopupWindow optionPopup;


    public VerifyEditText(Context context) {
        this(context, null);
    }

    public VerifyEditText(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VerifyEditText(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(context, attrs);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public VerifyEditText(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initAttrs(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 将控件宽度平局分为四等份
        final int blockWidth = getBlockWidth();
        final int textBaseLine = getDrawTextBaseLinePosition();
        int startX = 0;
        final int len = mOnlyShownLine ? (mCodeStr == null ? 0 : mCodeStr.length()) : mMaxLength;
        mPaint.setTypeface(getTypeFace(mTextStyle));
        mPaint.setTextSize(mTextSize);
        mPaint.setTextAlign(Paint.Align.CENTER);
        for (int i = 0; i < len; i++) {
            if (mCodeStr != null && i < mCodeStr.length()) {
                mTextBound.setEmpty();
                mPaint.getTextBounds(String.valueOf(mCodeStr.charAt(i)), 0, 1, mTextBound);
                mPaint.setColor(i == mCodeStr.length() - 1 ? mTextActiveColor : mTextInactiveColor);
                canvas.drawText(String.valueOf(mCodeStr.charAt(i)),
                        startX + blockWidth / 2f,
                        textBaseLine,
                        mPaint);
            }
            drawUnderline(canvas, startX + blockWidth / 2, textBaseLine, mLineWidth, !TextUtils.isEmpty(mCodeStr) && i == mCodeStr.length() - 1);
            startX += blockWidth;
        }
    }

    @Override
    public boolean onCheckIsTextEditor() {
        return true;
    }

    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        outAttrs.inputType = InputType.TYPE_CLASS_NUMBER;
        outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
        return new CodeVerifyInputConnection(this, true);
    }

    @Override
    protected void onDetachedFromWindow() {
        setFocusable(false);
        setFocusableInTouchMode(false);
        if (optionPopup != null) {
            optionPopup.dismiss();
            optionPopup = null;
        }
        mCompleteListener = null;
        onKeyEventListener = null;
        super.onDetachedFromWindow();
        clearFocus();
    }

    private float downX;
    private float downY;
    private boolean mPressed = false;
    private boolean longClicked = false;
    private final Runnable longPressCheckRunnable = new Runnable() {
        @Override
        public void run() {
            longClicked = true;
            boolean handled = false;
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                handled = performLongClick();
            } else {
                handled = performLongClick(downX, downY);
            }
            // 处理长按事件. by Administrator on 2022/3/14 11:11.
            if (!handled) {
                showOptionPopup((int) downX, (int) downY);
            }
        }
    };

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean consumed = true;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                longClicked = false;
                if (!mPressed) {
                    mPressed = true;
                    if (isLongClickable()) {
                        postDelayed(longPressCheckRunnable, ViewConfiguration.getLongPressTimeout());
                    }
                }
                break;

            case MotionEvent.ACTION_UP:
                mPressed = false;
                consumed = event.getX() - downX <= ViewConfiguration.get(getContext()).getScaledTouchSlop()
                        && event.getY() - downY <= ViewConfiguration.get(getContext()).getScaledTouchSlop();
                if (consumed && !longClicked) {
                    removeCallbacks(longPressCheckRunnable);
                    showIME();
                    if (isClickable()) {
                        performClick();
                    }
                }
                break;

            default:
                break;
        }
        return consumed;
    }

    public void setMaxLength(@IntRange(from = 4, to = 6) int length) {
        mMaxLength = length;
    }

    public int getMaxLength() {
        return mMaxLength;
    }

    public void setTextSize(int size) {
        mTextSize = size;
        mPaint.setTextSize(mTextSize);
        invalidate();
    }

    public void setCode(int code) {
        setCode(String.valueOf(code));
    }

    public void setCode(String code) {
        mCodeStr.setLength(0);
        mCodeStr.append(code);
    }

    public int getCode() {
        return TextUtils.isEmpty(mCodeStr) ? 0 : Integer.parseInt(mCodeStr.toString());
    }

    public String getCodeString() {
        return mCodeStr == null ? "" : mCodeStr.toString();
    }


    public void setTextActiveColor(@ColorInt int color) {
        mTextActiveColor = color;
    }

    public void setTextInactiveColor(@ColorInt int color) {
        mTextInactiveColor = color;
    }

    public void setLineActiveColor(@ColorInt int color) {
        mLineActiveColor = color;
    }

    public void setLineMarginTop(int topMargin) {
        mLineMarginTop = topMargin;
    }

    public void setLineHeight(int lineHeight) {
        mLIneHeight = lineHeight;
    }

    public void setLineWidth(int lineWidth) {
        mLineWidth = lineWidth;
    }

    public void setLineInactiveColor(@ColorInt int color) {
        mLineInactiveColor = color;
    }

    public void setTextStyle(@TypeFaceInt int textStyle) {
        this.mTextStyle = textStyle;
        mPaint.setTypeface(getTypeFace(textStyle));
        invalidate();
    }

    public void setOnlyShownLine(boolean isOnlyDrawShownLine) {
        mOnlyShownLine = isOnlyDrawShownLine;
    }

    public void setOnCompleteListener(@Nullable OnCompleteListener listener) {
        mCompleteListener = listener;
    }

    public void setOnKeyEventListener(@Nullable OnKeyEventListener listener) {
        this.onKeyEventListener = listener;
    }

    private void showIME() {
        //InputMethodManager来控制输入法弹起和缩回。
        setFocusable(true);
        setFocusableInTouchMode(true);
        requestFocus();
        InputMethodManager m = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        if (m != null) {
            m.showSoftInput(this, InputMethodManager.SHOW_FORCED);
        }
    }

    private void hideIME() {
        //InputMethodManager来控制输入法弹起和缩回。
        setFocusable(false);
        setFocusableInTouchMode(false);
        clearFocus();
        InputMethodManager m = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        if (m != null) {
            m.hideSoftInputFromWindow(getWindowToken(), 0);
        }
        setFocusableInTouchMode(true);
    }

    private void initAttrs(Context context, AttributeSet attrs) {
        final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.VerifyEditText);
        mMaxLength = typedArray.getInt(R.styleable.VerifyEditText_cvedt_maxLength, 4);
        mMaxLength = mMaxLength > 6 ? 6 : mMaxLength;
        mMaxLength = mMaxLength < 4 ? 4 : mMaxLength;
        String codeStr = typedArray.getString(R.styleable.VerifyEditText_cvedt_text);
        if (TextUtils.isEmpty(codeStr)) {
            codeStr = "";
        }
        if (codeStr.length() > mMaxLength) {
            codeStr = codeStr.substring(0, mMaxLength);
        }
        mCodeStr.setLength(0);
        mCodeStr.append(codeStr);
        mTextSize = typedArray.getDimensionPixelSize(R.styleable.VerifyEditText_cvedt_textSize, toPx(24));
        mTextInactiveColor = typedArray.getColor(R.styleable.VerifyEditText_cvedt_textInactiveColor, DEFAULT_INACTIVE_COLOR);
        mTextActiveColor = typedArray.getColor(R.styleable.VerifyEditText_cvedt_textActiveColor, DEFAULT_ACTIVE_COLOR);
        mLineInactiveColor = typedArray.getColor(R.styleable.VerifyEditText_cvedt_lineInactiveColor, DEFAULT_INACTIVE_COLOR);
        mLineActiveColor = typedArray.getColor(R.styleable.VerifyEditText_cvedt_lineActiveColor, DEFAULT_ACTIVE_COLOR);
        mLIneHeight = typedArray.getDimensionPixelSize(R.styleable.VerifyEditText_cvedt_lineHeight, toPx(DEFAULT_LINE_HEIGHT));
        mLineMarginTop = typedArray.getDimensionPixelSize(R.styleable.VerifyEditText_cvedt_lineMarginTop, toPx(DEFAULT_LINE_MARGIN_TOP));
        mLineWidth = typedArray.getDimensionPixelSize(R.styleable.VerifyEditText_cvedt_lineWidth, toPx(24));
        mOnlyShownLine = typedArray.getBoolean(R.styleable.VerifyEditText_cvedt_onlyShownLine, false);
        mTextStyle = typedArray.getInt(R.styleable.VerifyEditText_cvedt_textStyle, 0);
        typedArray.recycle();
        initPaint();
        setFocusable(true);
        setFocusableInTouchMode(true);
        setClickable(true);
        setLongClickable(true);
    }

    private void initPaint() {
        mPaint = new TextPaint();
        mPaint.setStrokeWidth(mLIneHeight);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setTextSize(mTextSize);
        mPaint.setTypeface(getTypeFace(mTextStyle));
        mPaint.setAntiAlias(true);
    }

    private int computeWidth() {
        return getPaddingLeft() + getPaddingRight() + computeTextWidth();
    }

    private int computeHeight() {
        return getPaddingTop() + getPaddingBottom() + computeTextHeight() + mLineMarginTop + mLIneHeight;
    }

    private int computeTextHeight() {
        final Paint.FontMetricsInt fmi = mPaint.getFontMetricsInt();
        return fmi.descent - fmi.ascent;
    }

    private int computeTextWidth() {
        if (!TextUtils.isEmpty(mCodeStr)) {
            return (int) mPaint.measureText(mCodeStr.toString());
        }
        return Math.max(mLineWidth, mTextSize);
    }

    private int getBlockWidth() {
        return getWidth() / mMaxLength;
    }


    private int measureWidth(int widthMeasureSpec) {
        final int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
        final int measureMode = MeasureSpec.getMode(widthMeasureSpec);
        int resultWidth = measureWidth;
        switch (measureMode) {

            case MeasureSpec.AT_MOST:
                resultWidth = computeWidth();
                break;

            case MeasureSpec.EXACTLY:
            case MeasureSpec.UNSPECIFIED:
                resultWidth = measureWidth;
                break;

            default:
                break;
        }
        return resultWidth;
    }

    private int measureHeight(int heightMeasureSpec) {
        final int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
        final int measureMode = MeasureSpec.getMode(heightMeasureSpec);
        int resultHeight = measureHeight;
        switch (measureMode) {

            case MeasureSpec.AT_MOST:
                resultHeight = computeHeight();
                break;

            case MeasureSpec.EXACTLY:
            case MeasureSpec.UNSPECIFIED:
                resultHeight = measureHeight;
                break;

            default:
                break;
        }
        return resultHeight;
    }

    private void drawUnderline(Canvas canvas, int centerX, int textBaseLine, int lineWidth, boolean isActive) {
        final int linePosition = textBaseLine + mLineMarginTop;
        mPaint.setColor(isActive ? mLineActiveColor : mLineInactiveColor);
        mPaint.setStrokeWidth(mLIneHeight);
        int halfW = lineWidth >> 1;
        canvas.drawLine(centerX - halfW, linePosition, centerX + halfW, linePosition, mPaint);
    }

    private int getDrawTextBaseLinePosition() {
        return getHeight() / 2 - mLineMarginTop - mLIneHeight + computeTextHeight() / 2;
    }

    private int toPx(int dp) {
        return (int) (dp * getResources().getDisplayMetrics().density + 0.5f);
    }

    private Typeface getTypeFace(@TypeFaceInt int typeFace) {
        return Typeface.defaultFromStyle(typeFace);
    }

    private class CodeVerifyInputConnection extends BaseInputConnection {

        CodeVerifyInputConnection(View targetView, boolean fullEditor) {
            super(targetView, fullEditor);
        }

        @Override
        public boolean commitText(CharSequence text, int newCursorPosition) {
            final boolean notCommitText = TextUtils.isEmpty(text)
                    || !TextUtils.isDigitsOnly(text)
                    || mCodeStr.length() >= mMaxLength;
            if (notCommitText) {
                return false;
            }
            mCodeStr.append(text);
            if (mCodeStr.length() == mMaxLength) {
                if (mCompleteListener != null) {
                    mCompleteListener.onComplete(mCodeStr.toString());
                    hideIME();
                }
            }
            invalidate();
            return true;
        }

        @Override
        public boolean deleteSurroundingText(int beforeLength, int afterLength) {
            if (TextUtils.isEmpty(mCodeStr) || afterLength < 0) {
                return false;
            }
            mCodeStr.deleteCharAt(afterLength);
            invalidate();
            return true;
        }

        @Override
        public boolean sendKeyEvent(KeyEvent event) {
            if (event.getAction() != 0)
                return false;
            if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
                return deleteSurroundingText(mCodeStr.length(), mCodeStr.length() - 1);
            }
            int number = event.getKeyCode() - 7;
            if (number >= 0 && number <= 9) {
                return commitText(String.valueOf(event.getKeyCode() - 7), mMaxLength);
            }
            boolean handled = onKeyEventListener != null && onKeyEventListener.onKeyEvent(event);
            if (!handled && event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
                handled = true;
                hideIME();
            }
            return handled;
        }

        @Override
        public void closeConnection() {
            super.closeConnection();
        }
    }

    private void showOptionPopup(int x, int y) {
        if (optionPopup == null) {
            OperationsPopupWindow optionPopup = new OperationsPopupWindow(getContext());
            optionPopup.setPasteCallback(new OperationsPopupWindow.OnPasteCallback() {
                String shownCode;

                @Override
                public void onPaste(OperationsPopupWindow popupWindow, String text) {
                    shownCode = text;
                    if (text.length() > mMaxLength) {
                        shownCode = text.substring(0, mMaxLength);
                    }
                    if (!mCodeStr.toString().equals(shownCode)) {
                        mCodeStr.setLength(0);
                        mCodeStr.append(shownCode);
                        invalidate();
                    }
                }
            });
            this.optionPopup = optionPopup;
        }
        optionPopup.showAsDropDown(VerifyEditText.this,
                x, y);
    }

}
